/* FreeRTOS includes. */
#include "FreeRTOS.h"
#include "task.h"
#include "queue.h"
#include "semphr.h"
#include "FreeRTOSConfig.h"

#include "i2c.h"
#include "LPC17xx.h"
#include "uart.h"
#include "clock.h"
#include "interrupt.h"
#include "status.h"
#include "pin_config.h"

volatile uint32_t i2c_state = I2C_IDLE;
volatile uint32_t i2c_timeout = 0;
volatile uint32_t read_index;
volatile uint32_t write_index;

volatile uint8_t i2c_buffer_write[BUFSIZE];
volatile uint8_t i2c_buffer_read[BUFSIZE];
volatile uint32_t i2c_read_len;
volatile uint32_t i2c_write_len;
uint32_t device_address;

xSemaphoreHandle xI2C_MASTER_Semaphore;

/**
 * I2C2 initialization
 *
 * @param  none
 * @return none
 *
 * @brief  initialize i2c
 *
 */
void i2c2_init(void)
{

	vSemaphoreCreateBinary( xI2C_MASTER_Semaphore);

	/* switch on power for i2c2 */
	SC->PCONP |= PCI2C2_POWERON;

	/* configure i2c pins */
	pin_configure(BANK_0, PIN_10, PIN_FUNCTION_2, PIN_MODE_PULLUP);
	pin_configure(BANK_0, PIN_11, PIN_FUNCTION_2, PIN_MODE_PULLUP);

	/* clear flags */
	I2C2->I2CONCLR = I2CONCLR_AAC | I2CONCLR_SIC | I2CONCLR_STAC | I2CONCLR_I2ENC;

	/* set frequency */
	I2C2->I2SCLL = MAIN_FREQUENCY / 400000;
	I2C2->I2SCLH = MAIN_FREQUENCY / 400000;

	/* set interrupt priority */
	NVIC_SetPriority(I2C2_IRQn, I2C2_NVIC_PRIO);

	/* Install interrupt handler */
	NVIC_EnableIRQ(I2C2_IRQn);

	/* enable interrupts */
	I2C2->I2CONSET = I2CONSET_I2EN;

	return;
}

/**
 * I2C2 master read
 *
 * @param  i2c_device_t *i2c_device I2C device to read write
 * @param  uint8_t addr device register address
 * @param  uint8_t *data write data
 *
 * @return none
 *
 * @brief  read on i2c2
 *
 */
uint8_t i2c_read(i2c_device_t *i2c_device, uint8_t addr, uint8_t *data)
{
	uint8_t ret;
	if (xSemaphoreTake( xI2C_MASTER_Semaphore, ( portTickType ) 10 ) == pdTRUE)
	{
		write_index=0;
		read_index=0;
		/* Write SLA(W), address, SLA(R), and read one byte back. */
		i2c_write_len = 1;
		i2c_read_len = 1;

		/* device address */
		device_address = i2c_device->address;
		/* register address */
		i2c_buffer_write[0] = addr;
		/* read data */
		i2c_buffer_read[0] = 0x00;

		/* start i2c transfer */
		if (I2C_OK != i2c2_transfer())
		{
			ret = STATUS_ERROR_ACCESS;
			ui_printf("I2C timeout!\n");
		}
		else
		{
			ret = STATUS_OK;
		}

		data[0] = i2c_buffer_read[0];

		xSemaphoreGive( xI2C_MASTER_Semaphore);
	}
	else
	{
		ui_printf("Couldn't get Master I2C semaphore (read) !\n");
		ret = STATUS_ERROR_ACCESS;
	}
	return ret;

}

uint8_t i2c_read_burst(i2c_device_t *i2c_device, uint8_t addr, uint8_t *data)
{
	uint8_t ret;
	if (xSemaphoreTake( xI2C_MASTER_Semaphore, ( portTickType ) 10 ) == pdTRUE)
	{
		write_index=0;
		read_index=0;
		/* Write SLA(W), address, SLA(R), and read one byte back. */
		i2c_write_len = 1;
		i2c_read_len = 6;

		/* device address */
		device_address = i2c_device->address;
		/* register address */
		i2c_buffer_write[0] = addr;
		/* device address + read bit */
		i2c_buffer_read[0] = 0x00;
		i2c_buffer_read[1] = 0x00;
		i2c_buffer_read[2] = 0x00;
		i2c_buffer_read[3] = 0x00;
		i2c_buffer_read[4] = 0x00;
		i2c_buffer_read[5] = 0x00;

		/* start i2c transfer */
		if (I2C_OK != i2c2_transfer())
		{
			ret = STATUS_ERROR_ACCESS;
			ui_printf("I2C timeout!\n");
		}
		else
		{
			ret = STATUS_OK;
		}

		data[0] = i2c_buffer_read[0];
		data[1] = i2c_buffer_read[1];
		data[2] = i2c_buffer_read[2];
		data[3] = i2c_buffer_read[3];
		data[4] = i2c_buffer_read[4];
		data[5] = i2c_buffer_read[5];

		xSemaphoreGive( xI2C_MASTER_Semaphore);
	}
	else
	{
		ui_printf("Couldn't get Master I2C semaphore (read) !\n");
		ret = STATUS_ERROR_ACCESS;
	}
	return ret;

}

/**
 * I2C2 master write
 *
 * @param  i2c_device_t *i2c_device I2C device to read write
 * @param  uint8_t addr device register address
 * @param  uint8_t *data write data
 *
 * @return none
 *
 * @brief  writes on i2c2
 *
 */
uint8_t i2c_write(i2c_device_t *i2c_device, uint8_t addr, uint8_t *data)
{
	uint8_t ret;

	if (xSemaphoreTake(xI2C_MASTER_Semaphore, ( portTickType ) 10 ) == pdTRUE)
	{
		write_index=0;
		read_index=0;
		/* Write SLA(W), address, SLA(W), data. */
		i2c_write_len = 2;
		i2c_read_len = 0;

		/* device address */
		device_address = i2c_device->address;
		/* register address */
		i2c_buffer_write[0] = addr;
		/* device address */
		i2c_buffer_write[1] = data[0];

		/* start i2c transfer */
		if (I2C_OK != i2c2_transfer())
		{
			ret = STATUS_ERROR_ACCESS;
			ui_printf("I2C timeout!\n");
		}
		else
		{
			ret = STATUS_OK;
		}

		/* release semaphore */
		xSemaphoreGive( xI2C_MASTER_Semaphore);

		ret = STATUS_OK;
	}
	else
	{
		ui_printf("Couldn't get Master I2C semaphore (write) !\n");
		ret = STATUS_ERROR_ACCESS;
	}
	return ret;
}

/**
 * I2C2 master interrupt service routine
 *
 * @param  none
 * @return none
 *
 * @brief  handles i2c transfers of master only bus
 *
 */
void i2c2_master_isr(void)
{
	/* get I2C state */
	uint8_t status = I2C2->I2STAT;

	/* reset timeout */
	i2c_timeout = 0;

	switch (status)
	{

	/* MASTER TRANSMITTER MODE */

	/* A Start/repeated start condition is issued. */
	case START_TRANSMITTED: //0x08
	case REP_START_ISSUED:  //0x10

		/* always send device address after start condition */
		I2C2->I2DAT = device_address;
		break;

	/* ACK for address or data received */
	case ADDRESS_W_ACK_RECEIVED: //0x18
	case DATA_W_ACK_RECEIVED:    //0x28

		/* something to write? */
		if ((write_index < i2c_write_len) && (i2c_write_len != 0))
		{
			/* write data byte */
			I2C2->I2DAT = i2c_buffer_write[write_index++];
			/* clear start bit */
			I2C2->I2CONCLR = I2CONCLR_STAC;
		}
		else if (i2c_read_len != 0)
		{
			/* set read bit */
			device_address |= 0x01;
			/* Set Repeated-start flag */
			I2C2->I2CONSET = I2CONSET_STA;
		}
		else
		{
			/* Set Stop flag */
			I2C2->I2CONSET = I2CONSET_STO;
			i2c_state = I2C_OK;
		}
		break;

		/* no ack received after sending data byte */
	case DATA_W_NO_ACK_RECEIVED:  //0x30

		/* Set Stop flag */
		I2C2->I2CONSET = I2CONSET_STO;
		i2c_state = I2C_NACK_ON_DATA;
		break;

		/* MASTER RECEIVER MODE */

	/* SLA+R has been transmitted; ACK has been received */
	case ADDRESS_R_ACK_RECEIVED:  //0x40

		/* last byte? */
		if ((read_index + 1) < i2c_read_len)
		{
			/* Will go to State 0x50 */
			/* assert ACK after data is received */
			I2C2->I2CONSET = I2CONSET_AA;

		}
		/* not intended */
		else
		{
			/* Will go to State 0x58 */
			/* assert NACK after data is received */
			I2C2->I2CONCLR = I2CONCLR_AAC;
		}
		I2C2->I2CONCLR = I2CONCLR_STAC;
		break;

		/* Data byte has been received, ack has been returned */
	case DATA_R_ACK_RECEIVED:

		i2c_buffer_read[read_index++] = I2C2->I2DAT;

		/* data intended to be received */
		if ((read_index + 1) < i2c_read_len)
		{
			/* assert ACK after data is received */
			I2C2->I2CONSET = I2CONSET_AA;
		}
		else
		{
			/* assert NACK on last byte */
			I2C2->I2CONCLR = I2CONCLR_AAC;
		}
		I2C2->I2CONCLR = I2CONCLR_STAC;
		break;

	/* Data byte has been received, NOT ack has been returned */
	case DATA_R_NO_ACK_RECEIVED:

		/* read data */
		i2c_buffer_read[read_index++] = I2C2->I2DAT;

		i2c_state = I2C_OK;

		/* Set Stop flag */
		I2C2->I2CONSET = I2CONSET_STO;
		break;

		/* regardless, it's a NACK */
	case NO_ACK_TRANSMITTER:
	case NO_ACK_RECEIVER:

		/* Set Stop flag */
		I2C2->I2CONSET = I2CONSET_STO;
		i2c_state = I2C_OK;
		break;

		/* Arbitration lost, no handling of multiple master situation */
	case ARBITRATION_LOST:
	default:
		i2c_state = I2C_ARBITRATION_LOST;
		I2C2->I2CONSET = I2CONSET_STO;
		break;
	}
	/* clear interrupt */
	I2C2->I2CONCLR = I2CONCLR_SIC;
}

/**
 * I2C2 transfer
 *
 * @param  none
 * @return uint32_t i2c0_state Actual state of I2C interface
 *
 * @brief  start i2c transfer
 *
 */
uint32_t i2c2_transfer(void)
{
	i2c_timeout = 0;

	/* set start flag */
	I2C2->I2CONSET |= I2CONSET_STA;

	i2c_state = I2C_BUSY;

	while (i2c_state == I2C_BUSY)
	{
		if (i2c_timeout >= MAX_TIMEOUT)
		{
			i2c_state = I2C_TIME_OUT;
			break;
		}
		i2c_timeout++;
	}
	I2C2->I2CONCLR = I2CONCLR_STAC;

	return i2c_state;
}

